package org.jjche.mybatis.interceptor;

import cn.hutool.extra.spring.SpringUtil;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;
import org.jjche.common.util.StrUtil;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.stereotype.Component;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.util.Properties;

/**
 * <p>
 * 动态替换dbName
 * </p>
 *
 * @author miaoyj
 * @since 2024-05-28
 */
@Component
//	标志该类是一个拦截器
//@Intercepts({
//        //指明该拦截器需要拦截哪一个接口的哪一个方法
//        @Signature(
//                type = Executor.class, //	四种类型接口中的某一个接口，如Executor.class。
//                method = "query", //对应接口中的某一个方法名，比如Executor的query方法。
//                //对应接口中的某一个方法的参数，比如Executor中query方法因为重载原因，有多个，args就是指明参数类型，从而确定是具体哪一个方法。
//                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
//        ),
//        @Signature(
//                type = Executor.class, //	四种类型接口中的某一个接口，如Executor.class。
//                method = "query", //对应接口中的某一个方法名，比如Executor的query方法。
//                //对应接口中的某一个方法的参数，比如Executor中query方法因为重载原因，有多个，args就是指明参数类型，从而确定是具体哪一个方法。
//                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
//        )})
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DbSchemaInterceptor implements Interceptor {
    private static String SCHEMA_NAME = "{schemaName}";
    /**
     * 进行拦截的时候要执行的方法
     *
     * @param invocation
     * @return
     * @throws Throwable
     */
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //通过MetaObject优雅访问对象的属性，这里是访问statementHandler的属性;：MetaObject是Mybatis提供的一个用于方便、
        //优雅访问对象属性的对象，通过它可以简化代码、不需要try/catch各种reflect异常，同时它支持对JavaBean、Collection、Map三种类型对象的操作。
//        MetaObject metaObject = MetaObject
//                .forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
//                        new DefaultReflectorFactory());
        //先拦截到RoutingStatementHandler，里面有个StatementHandler类型的delegate变量，其实现类是BaseStatementHandler，然后就到BaseStatementHandler的成员变量mappedStatement
//        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
        //id为执行的mapper方法的全路径名，如com.uv.dao.UserMapper.insertUser
//        String id = mappedStatement.getId();
        //sql语句类型 select、delete、insert、update
//        String sqlCommandType = mappedStatement.getSqlCommandType().toString();
        //数据库连接信息
        String dataSourceName = DynamicDataSourceContextHolder.peek();
        if (StrUtil.isNotBlank(dataSourceName)) {
           BoundSql boundSql = statementHandler.getBoundSql();
            //获取到原始sql语句，替换数据库前缀
            String sql = boundSql.getSql();
            if (StrUtil.contains(sql, SCHEMA_NAME) && StrUtil.containsIgnoreCase(sql, SCHEMA_NAME)) {
                String url = SpringUtil.getBean(DynamicDataSourceProperties.class).getDatasource().get(dataSourceName).getUrl();
                String dbName = cn.hutool.core.util.StrUtil.subAfter(url, cn.hutool.core.util.StrUtil.SLASH, true);
                dbName = cn.hutool.core.util.StrUtil.subBefore(dbName, "?", true);                sql = sql.replace(SCHEMA_NAME, dbName);
                //通过反射修改sql语句
                Field field = boundSql.getClass().getDeclaredField("sql");
                field.setAccessible(true);
                field.set(boundSql, sql);
            }
        }
        //执行结果
        return invocation.proceed();
    }

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    public void setProperties(Properties properties) {
    }

}
